/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.common.parsers;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.common.parsers.excel.ExcelPlainTextExtractor;
import com.blandware.atleap.common.parsers.exception.PlainTextExtractorException;
import com.blandware.atleap.common.parsers.exception.UnsupportedMimeTypeException;
import com.blandware.atleap.common.parsers.html.HTMLPlainTextExtractor;
import com.blandware.atleap.common.parsers.openoffice.OpenOfficePlainTextExtractor;
import com.blandware.atleap.common.parsers.pdf.PDFPlainTextExtractor;
import com.blandware.atleap.common.parsers.ppt.PowerPointPlainTextExtractor;
import com.blandware.atleap.common.parsers.rtf.RTFPlainTextExtractor;
import com.blandware.atleap.common.parsers.txt.TXTPlainTextExtractor;
import com.blandware.atleap.common.parsers.word.WordPlainTextExtractor;
import com.blandware.atleap.common.parsers.xml.XMLPlainTextExtractor;

import java.io.*;
import java.util.HashSet;
import java.util.Set;

/**
 * <p>
 * Used to extract a plain text from formatted documents. Currently the
 * following formats supported: MS Word, MS Excel Workbook, MS PowerPoint (97+),
 * RTF, PDF, HTML, XML and plain text.
 * </p>
 * <p>
 * To use, first instantiate with <code>new PlainTextExtractor()</code>, then
 * call the <code>extract</code> method.
 * </p>
 * <p>
 * One common ruleset is applied to figure out encoding to use during extracting
 * (first matched rule is used to obtain encoding):
 * <ol>
 * <li>
 * If encoding doesn't make sense for this format, it's totally ignored.
 * </li>
 * <li>
 * If extractor finds out the encoding from document by itself (for example,
 * HTML files often contain such information), it uses it.
 * </li>
 * <li>
 * If encoding is given to extractor as a parameter to <code>extract</code>
 * method, it's used.
 * </li>
 * <li>
 * Otherwise default encoding is used (it's currently
 * <code>Constants.DEFAULT_ENCODING</code>)
 * </li>
 * </ol>
 * </p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 * &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.5 $ $Date: 2007/12/22 15:49:19 $
 */
public class PlainTextExtractor {

    /*
     * Internal field used to store encoding that was used by extractor during
     * extracting process
     */
    protected String usedEncoding = null;

    protected static Set openOfficeFormats;
    static {
        openOfficeFormats = new HashSet();
        // OpenOffice 2.0 formats
        openOfficeFormats.add("application/vnd.oasis.opendocument.text");
        openOfficeFormats.add("application/vnd.oasis.opendocument.text-template");
        openOfficeFormats.add("application/vnd.oasis.opendocument.text-web");
        openOfficeFormats.add("application/vnd.oasis.opendocument.text-master");
        openOfficeFormats.add("application/vnd.oasis.opendocument.graphics");
        openOfficeFormats.add("application/vnd.oasis.opendocument.graphics-template");
        openOfficeFormats.add("application/vnd.oasis.opendocument.presentation");
        openOfficeFormats.add("application/vnd.oasis.opendocument.presentation-template");
        openOfficeFormats.add("application/vnd.oasis.opendocument.spreadsheet");
        openOfficeFormats.add("application/vnd.oasis.opendocument.spreadsheet-template");
        openOfficeFormats.add("application/vnd.oasis.opendocument.chart");
        openOfficeFormats.add("application/vnd.oasis.opendocument.formula");
        openOfficeFormats.add("application/vnd.oasis.opendocument.database");
        openOfficeFormats.add("application/vnd.oasis.opendocument.image");
        // OpenOffice 1.0 formats
        openOfficeFormats.add("application/vnd.sun.xml.writer");
        openOfficeFormats.add("application/vnd.sun.xml.writer.template");
        openOfficeFormats.add("application/vnd.sun.xml.calc");
        openOfficeFormats.add("application/vnd.sun.xml.calc.template");
        openOfficeFormats.add("application/vnd.sun.xml.draw");
        openOfficeFormats.add("application/vnd.sun.xml.draw.template");
        openOfficeFormats.add("application/vnd.sun.xml.impress");
        openOfficeFormats.add("application/vnd.sun.xml.impress.template");
        openOfficeFormats.add("application/vnd.sun.xml.writer.global");
        openOfficeFormats.add("application/vnd.sun.xml.math");
    }

    /**
     * Constructs new PlainTextExtractor instance
     */
    public PlainTextExtractor() {}

    /**
     * Extracts a plain text from a formatted document to a given writer.
     *
     * @param input the stream that supplies the document
     * @param mimeType the mime type of the document
     * @param output the writer which will accept the extracted text
     * @param encoding the encoding of the document in the stream. If the
     * <code>encoding</code> is <code>null</code>, then the extractor uses
     * its default encoding (currently all extractors use
     * <code>Constants.DEFAULT_ENCODING</code>).
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public void extract(InputStream input, String mimeType, Writer output,
                        String encoding) throws UnsupportedMimeTypeException,
            PlainTextExtractorException {
        SpecificPlainTextExtractor extractor;

        if (mimeType == null) {
            throw new IllegalArgumentException("mimeType parameter is null");
        }
        if (mimeType.equals("application/msword")) {
            extractor = new WordPlainTextExtractor();
        } else if (mimeType.equals("application/vnd.ms-excel")) {
            extractor = new ExcelPlainTextExtractor();
        } else if (mimeType.equals("application/vnd.ms-powerpoint")) {
            extractor = new PowerPointPlainTextExtractor();
        } else if (mimeType.equals("application/pdf")) {
            extractor = new PDFPlainTextExtractor();
        } else if (mimeType.equals("application/rtf")) {
            extractor = new RTFPlainTextExtractor();
        } else if (mimeType.equals("text/html")) {
            extractor = new HTMLPlainTextExtractor();
        } else if (mimeType.equals("application/xhtml+xml")) {
            extractor = new HTMLPlainTextExtractor();
        } else if (mimeType.equals("text/xml")) {
            extractor = new XMLPlainTextExtractor();
        } else if (mimeType.equals("text/plain")) {
            extractor = new TXTPlainTextExtractor();
        } else if (openOfficeFormats.contains(mimeType)) {
            extractor = new OpenOfficePlainTextExtractor();
        } else {
            throw new UnsupportedMimeTypeException("This mimeType is not supported: "
                                                   + mimeType);
        }
        extractor.extract(input, output, encoding);
        usedEncoding = extractor.getUsedEncoding();
    }

    /**
     * Extracts a plain text from a formatted document and returns it as a
     * string.
     *
     * @param input the stream that supplies the document
     * @param mimeType the mime type of the document
     * @param encoding the encoding of the document in the stream. If the
     * <code>encoding</code> is <code>null</code>, then the extractor uses
     * its default encoding (currently all extractors use
     * <code>Constants.DEFAULT_ENCODING</code>).
     * @return the extracted text as a string
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public String extract(InputStream input, String mimeType, String encoding)
            throws UnsupportedMimeTypeException, PlainTextExtractorException {
        StringWriter writer = new StringWriter();
        extract(input, mimeType, writer, encoding);
        return writer.toString();
    }

    /**
     * Extracts a plain text from a formatted document to a given writer. The
     * document is assumed to have the default encoding.
     *
     * @param input the stream that supplies the document
     * @param mimeType the mime type of the document
     * @param output the writer which will accept the extracted text
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public void extract(InputStream input, String mimeType, Writer output)
            throws UnsupportedMimeTypeException, PlainTextExtractorException {
        extract(input, mimeType, output, null);
    }

    /**
     * Extracts a plain text from a formatted document and returns it as a string.
     * The document is assumed to have the default encoding.
     *
     * @param input the stream that supplies the document
     * @param mimeType the mime type of the document
     * @return the extracted text as a string
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public String extract(InputStream input, String mimeType)
            throws UnsupportedMimeTypeException, PlainTextExtractorException {
        return extract(input, mimeType, (String)null);
    }

    /**
     * Extracts a plain text from a formatted document to a given writer. The
     * document is given as a <code>String</code>.
     *
     * @param input the string that supplies the document
     * @param mimeType the mime type of the document
     * @param output the writer which will accept the extracted text
     * @param encoding the encoding of the document in the stream. If the
     * <code>encoding</code> is <code>null</code>, then the extractor uses
     * its default encoding (currently all extractors use
     * <code>Constants.DEFAULT_ENCODING</code>). Also, that encoding is used to
     * convert an input string to a byte stream (if not given, it is again
     * assumed to be <code>Constants.DEFAULT_ENCODING</code>).
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public void extract(String input, String mimeType, Writer output,
                        String encoding) throws UnsupportedMimeTypeException,
            PlainTextExtractorException {
        try {
            extract(stringToInputStream(input, encoding), mimeType,
                    output, encoding);
        } catch (UnsupportedEncodingException e) {
            throw new PlainTextExtractorException(e);
        }
    }

    /**
     * Extracts a plain text from a formatted document and returns it as a
     * string. The document is given as a <code>String</code>.
     *
     * @param input the string that supplies the document
     * @param mimeType the mime type of the document
     * @param encoding the encoding of the document in the stream. If the
     * <code>encoding</code> is <code>null</code>, then the extractor uses
     * its default encoding (currently all extractors use
     * <code>Constants.DEFAULT_ENCODING</code>). Also, that encoding is used to
     * convert an input string to a byte stream (if not given, it is again
     * assumed to be <code>Constants.DEFAULT_ENCODING</code>).
     * @return the extracted text as a string
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public String extract(String input, String mimeType, String encoding)
            throws UnsupportedMimeTypeException, PlainTextExtractorException {
        try {
            return extract(stringToInputStream(input, encoding), mimeType,
                           encoding);
        } catch (UnsupportedEncodingException e) {
            throw new PlainTextExtractorException(e);
        }
    }

    /**
     * Extracts a plain text from a formatted document to a given writer. The
     * document is assumed to have the default encoding. The document is given
     * as a <code>String</code>, which is decoded to bytes using default
     * encoding too.
     *
     * @param input the string that supplies the document
     * @param mimeType the mime type of the document
     * @param output the writer which will accept the extracted text
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public void extract(String input, String mimeType, Writer output)
            throws UnsupportedMimeTypeException, PlainTextExtractorException {
        extract(input, mimeType, output, null);
    }

    /**
     * Extracts a plain text from a formatted document and returns it as a string.
     * The document is assumed to have the default encoding. The document
     * is given as a <code>String</code>, which is decoded to bytes using
     * default encoding too.
     *
     * @param input the string that supplies the document
     * @param mimeType the mime type of the document
     * @return the extracted text as a string
     * @throws UnsupportedMimeTypeException throwed when a given mime type is
     * not supported
     * @throws PlainTextExtractorException any other exception raised during
     * extracting
     */
    public String extract(String input, String mimeType)
            throws UnsupportedMimeTypeException, PlainTextExtractorException {
        return extract(input, mimeType, (String) null);
    }

    /**
     * <p>
     * Returns encoding that was used for extracting. If encoding has no sense
     * for particular document format or it's unknown for extractor, returns
     * <code>null</code>.
     * </p>
     * <p>
     * This method should be called after calling <code>extract</code>; before
     * it this method may return anything.
     * </p>
     *
     * @return encoding used or <code>null</code>
     */
    public String getUsedEncoding() {
        return usedEncoding;
    }

    /**
     * Converts a <code>String</code> to an <code>InputStream</code> using given
     * <code>encoding</code>. If the <code>encoding</code> is <code>null</code>,
     * it's assumed to be a default encoding
     * (<code>Constants.DEFAULT_ENCODING</code>).
     * @param input the string to be converted
     * @param encoding the encoding
     * @return an InputStream that supplies bytes from the <code>string</code>
     * @throws UnsupportedEncodingException if encoding is not supported
     */
    protected InputStream stringToInputStream(String input, String encoding)
            throws UnsupportedEncodingException {
        if (encoding == null || encoding.trim().length() == 0) {
            encoding = Constants.DEFAULT_ENCODING;
        }
        return new ByteArrayInputStream(input.getBytes(encoding));
    }
}
